package common

import (
	"context"
	"errors"
	"net"
	"shell/common/packages"
	"shell/global"
	"shell/utils"
	"sync/atomic"
	"time"

	"github.com/quic-go/quic-go"
)

// 对quic原始连接和流的包装，简化业务代码

const (
	CloseByProcess = "close by process"
	CloseAuto      = "close auto"
)

var (
	ErrCloseAlready = errors.New("close already")
	Logger          = global.DefaultLogger()
)

func IsNetErr(err error) net.Error {
	netErr, ok := err.(net.Error)
	if ok {
		return netErr
	}
	return nil
}

func IsAppError(err error) *quic.ApplicationError {
	appErr, ok := err.(*quic.ApplicationError)
	if ok {
		return appErr
	}
	return nil
}

func IsStreamErr(err error) *quic.StreamError {
	streamErr, ok := err.(*quic.StreamError)
	if ok {
		return streamErr
	}
	return nil
}

func HandleErr(err error) {
	if ne := IsNetErr(err); ne != nil {
		if ne.Timeout() {
			Logger.Errorf(1, "error quic network error: timeout")
		} else {
			Logger.Errorf(1, "error quic network error: not timeout")
		}
		return
	}

	if ae := IsAppError(err); ae != nil {
		if ae.Remote {
			Logger.Errorf(1, "close by remote, msg: %s", ae.ErrorMessage)
		} else {
			Logger.Errorf(1, "close by local, msg: %s", ae.ErrorMessage)
		}
		return
	}

	if se := IsStreamErr(err); se != nil {
		if se.Remote {
			Logger.Errorf(1, "stream error by remote, err code: %d", se.ErrorCode)
		} else {
			Logger.Errorf(1, "stream error by local, err code: %d", se.ErrorCode)
		}
		return
	}
	Logger.Errorf(1, "error: %v, type: %T", err, err)
}

type QuicConn struct {
	Conn     quic.Connection
	ctx      context.Context    // 在调用cancel和连接断开时，ctx.Done会返回
	cancel   context.CancelFunc // 调用该方法会停止AcceptStream和OpenStream方法，并导致连接关闭
	IsConn   atomic.Bool        // 指示quic连接是否可用
	bytePool *utils.BytesPool
}

// 创建一个QuicConn，并在连接断开时释放资源
func NewQuicConn(conn quic.Connection, pool *utils.BytesPool) *QuicConn {
	result := new(QuicConn)
	result.Conn = conn
	ctx, cancel := context.WithCancel(conn.Context())
	result.ctx = ctx
	result.cancel = cancel
	result.IsConn = atomic.Bool{}
	result.IsConn.Store(true)
	result.bytePool = pool
	go func() {
		<-ctx.Done()
		result.Close(CloseAuto)
	}()
	return result
}

func (qc *QuicConn) Ctx() context.Context {
	return qc.ctx
}

func (qc *QuicConn) Cancel() {
	qc.cancel()
}

// 当发生错误时，会自动关闭quic连接
func (qc *QuicConn) AcceptStream() (*QuicStream, error) {
	if !qc.IsConn.Load() {
		return nil, errors.New("connection is closed")
	}
	stream, err := qc.Conn.AcceptStream(qc.ctx)
	if err != nil {
		_ = qc.Conn.CloseWithError(quic.ApplicationErrorCode(quic.NoError), CloseByProcess)
		qc.cancel()
		qc.IsConn.Store(false)
		return nil, err
	}
	qs := new(QuicStream)
	qs.Conn = qc
	qs.Stream = stream
	qs.IsConn = atomic.Bool{}
	qs.IsConn.Store(true)
	return qs, nil
}

// 当发生错误时，会自动关闭quic连接
func (qc *QuicConn) OpenStream() (*QuicStream, error) {
	if !qc.IsConn.Load() {
		return nil, errors.New("connection is closed")
	}
	stream, err := qc.Conn.OpenStreamSync(qc.ctx)
	if err != nil {
		_ = qc.Conn.CloseWithError(quic.ApplicationErrorCode(quic.NoError), CloseByProcess)
		qc.cancel()
		qc.IsConn.Store(false)
		return nil, err
	}
	qs := new(QuicStream)
	qs.Conn = qc
	qs.Stream = stream
	qs.IsConn = atomic.Bool{}
	qs.IsConn.Store(true)
	return qs, nil
}

func (qc *QuicConn) Close(str string) {
	if qc.IsConn.CompareAndSwap(true, false) {
		_ = qc.Conn.CloseWithError(quic.ApplicationErrorCode(quic.NoError), str)
		qc.cancel()
	}
}

type QuicStream struct {
	Stream quic.Stream
	Conn   *QuicConn
	IsConn atomic.Bool
}

func (qs *QuicStream) Read(p []byte) (int, error) {
	if qs.IsConn.Load() {
		n, err := qs.Stream.Read(p)
		if err != nil {
			_ = qs.Close()
		}
		return n, err
	}
	return 0, ErrCloseAlready
}

func (qs *QuicStream) Write(p []byte) (int, error) {
	if qs.IsConn.Load() {
		n, err := qs.Stream.Write(p)
		if err != nil {
			_ = qs.Close()
		}
		return n, err
	}
	return 0, ErrCloseAlready
}

func (qs *QuicStream) Close() error {
	if qs.IsConn.CompareAndSwap(true, false) {
		return qs.Stream.Close()
	}
	return ErrCloseAlready
}

func (qs *QuicStream) CloseConn(str string) {
	_ = qs.Close()
	qs.Conn.Close(str)
}

func (qs *QuicStream) WritePackage(pointer any) error {
	return WritePackage(qs, pointer)
}

func (qs *QuicStream) ReadPackage(pointer any) error {
	return ReadPackage(qs, qs.Conn.bytePool, pointer)
}

func (qs *QuicStream) ReadPackageAfterDetect(pointer any, metadata *packages.PackageMetadata) error {
	return ReadPackageAfterDetect(qs, qs.Conn.bytePool, pointer, metadata)
}

// 发送关闭流的请求，并关闭流
func (qs *QuicStream) CloseStreamSync(role packages.RctlRole, closeForce bool) error {
	req := packages.NewCloseConnReq(role, true, false)
	err := WritePackage(qs, req)
	if err != nil {
		_ = qs.Close()
		return err
	}
	resp := new(packages.CloseConnResp)
	err = ReadPackage(qs, qs.Conn.bytePool, resp)
	if err != nil {
		_ = qs.Close()
		return err
	}
	if resp.Accept {
		time.Sleep(time.Millisecond * 100)
		_ = qs.Close()
		return nil
	}
	if closeForce {
		time.Sleep(time.Millisecond * 100)
		_ = qs.Close()
		return nil
	} else {
		return ErrNotCloseStream
	}
}

// 发送关闭quic连接的请求
func (qs *QuicStream) CloseConnSync(role packages.RctlRole, closeForce bool) error {
	req := packages.NewCloseConnReq(role, true, true)
	err := WritePackage(qs, req)
	if err != nil {
		qs.CloseConn(CloseByProcess)
		return err
	}
	resp := new(packages.CloseConnResp)
	err = ReadPackage(qs, qs.Conn.bytePool, resp)
	if err != nil {
		qs.CloseConn(CloseByProcess)
		return err
	}
	if resp.Accept {
		time.Sleep(time.Millisecond * 100)
		qs.CloseConn(CloseByProcess)
		return nil
	}
	if closeForce {
		time.Sleep(time.Millisecond * 100)
		qs.CloseConn(CloseByProcess)
		return nil
	} else {
		return ErrNotCloseStream
	}
}

// 等待接收对端发送的关闭请求，closeConn表示是否关闭quic连接
func (qs *QuicStream) WaitForClose(role packages.RctlRole, closeConn bool) {
	go qs.WaitForCloseSync(role, closeConn)
}

func (qs *QuicStream) WaitForCloseSync(role packages.RctlRole, closeConn bool) {
	req := new(packages.CloseConnReq)
	ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
	defer cancel()
	err := ReadPackage(qs, qs.Conn.bytePool, req)
	if err != nil {
		// HandleErr(err)
		<-ctx.Done()
		if closeConn {
			qs.CloseConn(CloseByProcess)
		}
		return
	}
	resp := req.ToResp(role, true)
	err = WritePackage(qs, resp)
	if err != nil {
		// HandleErr(err)
		<-ctx.Done()
		if closeConn {
			qs.CloseConn(CloseByProcess)
		}
		return
	}
	if req.CloseConn {
		<-ctx.Done()
		qs.CloseConn(CloseByProcess)
		return
	}
	<-ctx.Done()
	_ = qs.Close()
}

func (qs *QuicStream) DetectPackage() (*packages.PackageMetadata, error) {
	return DetectPackage(qs)
}
